package com.irain.pp.tool.aop.sync;

import com.irain.pp.tool.aop.sync.annotation.SyncLock;
import com.irain.pp.tool.api.monitor.MethodMonitorHelper;
import com.irain.pp.tool.api.syn.LockHelper;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;


/**
 * @author WangYanPeng
 * @date 2017/11/3 14:58
 */
@Aspect
@Component
public class SyncLockAspect {

    private static final ExpressionParser PARSER = new SpelExpressionParser();
    private static final LocalVariableTableParameterNameDiscoverer DISCOVERER = new LocalVariableTableParameterNameDiscoverer();

    @Around("@annotation(syncLock)")
    public Object lock(ProceedingJoinPoint pjp, SyncLock syncLock) throws Throwable {
        String[] values = syncLock.value();
        String[] keys;
        Method method = ((MethodSignature) pjp.getSignature()).getMethod();
        if (values.length != 0) {
            EvaluationContext context = genContext(method, pjp.getArgs());
            keys = genKeys(values, context);
        } else {
            keys = new String[]{};
        }
        return LockHelper.syncLock(pjp.getTarget(), keys, () -> {
            if (syncLock.isMonitored()){
                MethodMonitorHelper.updateMethodInfo(method);
            }
            return pjp.proceed(pjp.getArgs());
        }, syncLock.expire(), syncLock.timeout());
    }

    private EvaluationContext genContext(Method method,Object[] paramValues){
        String[] paramNames = DISCOVERER.getParameterNames(method);
        EvaluationContext context = new StandardEvaluationContext();
        for (int i = 0, len = paramNames.length; i < len; i++) {
            context.setVariable(paramNames[i], paramValues[i]);
        }
        return context;
    }

    private String[] genKeys(String[] values,EvaluationContext context){
        String[] keys = new String[values.length];
        for (int i = 0, len = values.length; i < len; i++) {
            Expression expression = PARSER.parseExpression(values[i]);
            keys[i] = (String) expression.getValue(context);
        }
        return keys;
    }

}
